package com.rabbit.aspect;

import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONObject;
import com.rabbit.annotation.Log;
import com.rabbit.api.RemoteApiService;
import com.rabbit.constant.SecurityConstant;
import com.rabbit.db.entity.OperLog;
import com.rabbit.db.entity.User;
import com.rabbit.exception.BusinessException;
import com.rabbit.executor.AsyncTask;
import com.rabbit.handler.MdcRunnable;
import com.rabbit.mapper.OperLogMapper;
import com.rabbit.util.IpUtil;
import com.rabbit.util.RequestUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;

/**
 * <h1>操作日志切面</h1>
 *
 * @author zjw
 * @date 2022/2/8 9:31
 */
@Aspect
@Component
@Order(2)
public class OperLogAspect {

    private Logger logger = LoggerFactory.getLogger(OperLogAspect.class);

    @Resource
    private RemoteApiService remoteApiService;

    @Resource
    private OperLogMapper operLogMapper;

    /**
     * 处理完后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "@annotation(log)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Log log, Object jsonResult) {
        handleLog(joinPoint, log, null, jsonResult);
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(value = "@annotation(log)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Log log, Exception e) {
        handleLog(joinPoint, log, e, null);
    }

    /**
     * 记录日志
     */
    private void handleLog(JoinPoint joinPoint, Log log, Exception e, Object jsonResult) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
        // 获取当前登录用户
        SaSession tokenSession = StpUtil.getTokenSession();
        User user = (User) tokenSession.get(SecurityConstant.LOGIN_USER);
        // 记录操作日志
        OperLog operLog = OperLog.builder().build();
        operLog.setModuleName(log.name());
        operLog.setOperAccount(user.getLoginAccount());
        operLog.setOperType(log.type().getValue());
        operLog.setOperTime(DateUtil.date());
        operLog.setOperUrl(request.getRequestURI());
        operLog.setIsSuccess(true);
        operLog.setOperIp(IpUtil.getClientIP(request));
        // 方法名称
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        operLog.setMethod(className + "." + methodName + "()");
        // 请求方式
        operLog.setRequestMethod(request.getMethod());
        // 请求参数和响应
        String requestParams = RequestUtil.getRequestParams(joinPoint);
        operLog.setOperParam(requestParams.substring(0, Math.min(requestParams.length(), 1000)));
        String result = JSONObject.toJSONString(jsonResult);
        if (e != null) {
            operLog.setIsSuccess(false);
            if (e instanceof BusinessException) {
                operLog.setErrorMsg(((BusinessException) e).getResultCodeEnum().getMessage());
            } else {
                operLog.setErrorMsg(e.getMessage().substring(0, Math.min(e.getMessage().length(), 2000)));
            }
        } else {
            operLog.setJsonResult(result.substring(0, Math.min(result.length(), 1000)));
        }
        // 操作地点
        operLog.setOperLocation(remoteApiService.getOperLocation(operLog.getOperIp()));
        AsyncTask.COMMON_EXECUTOR.execute(new MdcRunnable(() -> {
            logger.info("*** 记录操作日志开始 ***");
            operLogMapper.insertSelective(operLog);
            logger.info("*** 记录操作日志结束 ***");
        }));
    }
}
